package org.xarcher.emiya.utils

import com.sksamuel.elastic4s.Index
import com.sksamuel.elastic4s.embedded.LocalNode
import io.circe.generic.auto._
import com.sksamuel.elastic4s.http.ElasticDsl._
import com.sksamuel.elastic4s.http.index.CreateIndexResponse
import com.sksamuel.elastic4s.http.{
  HttpClient,
  HttpRequestClient,
  RequestFailure,
  RequestSuccess
}
import com.sksamuel.elastic4s.mappings.dynamictemplate.DynamicMapping
import org.slf4j.LoggerFactory
import play.api.http.HeaderNames
import play.api.inject.ApplicationLifecycle
import play.api.libs.ws.WSClient

import scala.concurrent.ExecutionContext
import scala.util.{ Failure, Success, Try }
import scala.concurrent.Future

class EmbeddedServer(
  shutdownHook: ApplicationLifecycle,
  wsClientE4SRequestClient: WsClientE4SRequestClient)(
  implicit
  executionContext: ExecutionContext) {

  val index: Index = "overlrd010"
  val typeName: String = "html_content"

  val logger = LoggerFactory.getLogger(getClass)

  protected lazy val initEs: Future[HttpClient] = {
    Future {
      val localNode = LocalNode("overlord001", "./esTmp/tmpDataPath004")
      //val client = localNode.http(true)
      val client = new HttpClient {
        private val delegate =
          new wsClientE4SRequestClient.SttpRequestHttpClient(
            s"elasticsearch://${localNode.host}:${localNode.port}")
        override def client: HttpRequestClient = delegate
        override def close(): Unit = {
          localNode.stop()
        }
      }
      shutdownHook.addStopHook(() =>
        Future {
          Try {
            logger.info("开始关闭 elasticSearch 客户端")
            client.close()
            //client.client.close()
            //client.close()
          } match {
            case Failure(e) => logger.error("关闭 elasticSearch 客户端遇到错误", e)
            case Success(_) => logger.info("关闭 elasticSearch 客户端成功")
          }
        })
      client
    }.andThen {
      case Failure(e) =>
        logger.error("创建 elasticSearch 实例遇到错误", e)
      case Success(_) =>
        logger.info("创建 elasticSearch 实例成功")
    }
  }

  val esLocalClient: Future[HttpClient] = {
    createIndexInitAction.flatMap {
      _: Either[RequestFailure, RequestSuccess[CreateIndexResponse]] =>
        initEs
    }
  }

  protected lazy val createIndexInitAction = {
    initEs
      .flatMap { client =>
        val action1 = client.execute {
          createIndex(index.name).mappings(
            mapping(typeName)
              .fields(
                keywordField("url"),
                keywordField("contentType"),
                keywordField("host"),
                keywordField("protocol"),
                keywordField("referer"),
                intField("deep"),
                longField("wasteTime"),
                dateField("requestTime").format("epoch_millis"),
                intField("status"),
                intField("contentLength"),
                //booleanField("needFetchedChildern"),
                //booleanField("fetchedChildern"),
                intField("planId"),
                intField("actionId"),
                nestedField("subs")
                  .fields(
                    keywordField("url"),
                    intField("path"),
                    keywordField("targetClass"))
                  .dynamic(false), //TODO 测试 dynamic，例如去除某个看会不会报 exception
                textField("body"),
                textField("textBody"),
                objectField("law_body").dynamic(true),
                objectField("law_text_body").dynamic(true),
                keywordField("search_law_body"),
                keywordField("search_law_text_body"),
                textField("cssBody"))
              .dynamic(DynamicMapping.Strict)
              .dynamicTemplates(
                dynamicTemplate("law_body_dyn")
                  .mapping(dynamicKeywordField().copyTo("search_law_body"))
                  .pathMatch("law_body.law_body_*"),
                dynamicTemplate("law_text_body_dyn")
                  .mapping(dynamicKeywordField().copyTo("search_law_text_body"))
                  .pathMatch("law_text_body.law_text_body_*")))
        }
        lazy val action2 = client.execute {
          updateIndexLevelSettings(index.name).maxResultWindow(Int.MaxValue)
        }
        action1.flatMap(r => action2.map(_ => r))
      }
  }

}

import java.io._
import com.sksamuel.elastic4s.ElasticsearchClientUri
import com.sksamuel.elastic4s.http.HttpEntity.{
  FileEntity,
  InputStreamEntity,
  StringEntity
}
import com.sksamuel.elastic4s.http.{ HttpEntity, HttpRequestClient, HttpResponse }
import scala.concurrent.Future
import scala.io.Source
import play.api.libs.ws._

class WsClientE4SRequestClient(wsClient: WSClient) {

  class SttpRequestHttpClient(clientUri: ElasticsearchClientUri)(
    implicit
    ex: ExecutionContext)
    extends HttpRequestClient {

    override def async(
      method: String,
      endpoint: String,
      params: Map[String, Any]): Future[HttpResponse] = {
      val r = wsClient.url(
        s"http://${clientUri.hosts.head._1}:${clientUri.hosts.head._2}$endpoint")
      val r1 = params.foldLeft(r) { (request, map) =>
        request.withQueryStringParameters(map._1 -> map._2.toString)
      }
      val response = method.toUpperCase match {
        case "GET" => r1.get()
        case "HEAD" => r1.head()
        case "POST" => r1.post("")
        case "PUT" => r1.put("")
        case "DELETE" => r1.delete()
      }
      response.map { s =>
        val entity =
          HttpEntity.StringEntity(
            s.bodyAsBytes.decodeString("utf-8"),
            Option(s.contentType))
        HttpResponse(
          s.status,
          Option(entity),
          s.headers.map(s => s._1 -> s._2.headOption).collect {
            case (key, Some(value)) => key -> value
          })
      }
    }

    override def async(
      method: String,
      endpoint: String,
      params: Map[String, Any],
      entity: HttpEntity): Future[HttpResponse] = {
      val r = wsClient.url(
        s"http://${clientUri.hosts.head._1}:${clientUri.hosts.head._2}$endpoint")
      val r0 = params.foldLeft(r) { (request, map) =>
        request.withQueryStringParameters(map._1 -> map._2.toString)
      }
      val r1 = entity.contentType match {
        case Some(contentType) =>
          r0.withHttpHeaders(HeaderNames.CONTENT_TYPE -> contentType)
        case _ => r0
      }
      val r2 = (entity -> method.toUpperCase) match {
        case (StringEntity(content: String, _), "GET") =>
          r1.withBody(content).get()
        case (StringEntity(content: String, _), "HEAD") =>
          r1.withBody(content).head()
        case (StringEntity(content: String, _), "POST") => r1.post(content)
        case (StringEntity(content: String, _), "PUT") => r1.put(content)
        case (StringEntity(content: String, _), "DELETE") =>
          r1.withBody(content).delete()
        case (InputStreamEntity(in: InputStream, _), "GET") =>
          r1.withBody(
            Source.fromInputStream(in, "UTF-8").getLines().mkString("\n"))
            .get()
        case (InputStreamEntity(in: InputStream, _), "HEAD") =>
          r1.withBody(
            Source.fromInputStream(in, "UTF-8").getLines().mkString("\n"))
            .head()
        case (InputStreamEntity(in: InputStream, _), "POST") =>
          r1.post(Source.fromInputStream(in, "UTF-8").getLines().mkString("\n"))
        case (InputStreamEntity(in: InputStream, _), "PUT") =>
          r1.put(Source.fromInputStream(in, "UTF-8").getLines().mkString("\n"))
        case (InputStreamEntity(in: InputStream, _), "DELETE") =>
          r1.withBody(
            Source.fromInputStream(in, "UTF-8").getLines().mkString("\n"))
            .delete()
        case (FileEntity(file: File, _), "POST") => r1.post(file)
        case (FileEntity(file: File, _), "PUT") => r1.put(file)
      }
      r2.map { s =>
        val entity =
          HttpEntity.StringEntity(
            s.bodyAsBytes.decodeString("utf-8"),
            Option(s.contentType))
        HttpResponse(
          s.status,
          Option(entity),
          s.headers.map(s => s._1 -> s._2.headOption).collect {
            case (key, Some(value)) => key -> value
          })
      }
    }

    override def close(): Unit = {}
  }

}
